// hash.cs
// ------------------------------------------------------------------
//
// A command line tool to compute and display md5 or sha1 hash, or both,
// of a file.
//
// Compile into hash.exe to compute and display both.  Rename or copy
// that EXE to md5.exe to display only the MD5 hash.  Rename or copy it
// to sha1.exe to display only the SHA1 hash.
//
//
// last saved:
// Time-stamp: <2012-April-19 15:39:49>
// ------------------------------------------------------------------
//
// Copyright (c) 2009-2010 by Dino Chiesa
// All rights reserved!
//
// ------------------------------------------------------------------

using System;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;


// to allow fast ngen
[assembly: AssemblyTitle("hash.cs")]
[assembly: AssemblyDescription("command line tool to compute and display MD5 and/or SHA1 hash of a file.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Dino Chiesa")]
[assembly: AssemblyProduct("Tools")]
[assembly: AssemblyCopyright("Copyright (c) Dino Chiesa 2009-2010")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.1.1.3")]


// Test cases:
//
// "" => d41d8cd98f00b204e9800998ecf8427e
// a => 0cc175b9c0f1b6a831c399e269772661
// abc => 900150983cd24fb0d6963f7d28e17f72
// message digest => f96b697d7cb7938d525a2f31aaf161d0
// abcdefghijklmnopqrstuvwxyz => c3fcd3d76192e4007dfb496cca67e13b
// ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 => d174ab98d277d9f5a5611c2c9f419d9f
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890 => 57edf4a22be3c955ac49da2e2107b67a
// The quick brown fox jumps over the lazy dog => 9e107d9d372bb6826bd81d3542a419d6
// The quick brown fox jumps over the lazy cog => 1055d3e698d289f2af8663725127bd4b


namespace Ionic.ToolsAndTests
{
    /// <summary>
    ///   a class for extension methods.
    /// </summary>
    public static class StaticExtensions
    {
        /// <summary>
        ///   an extension method that returns the short name of a hash algorithm, based on
        ///   its actual type name.
        /// </summary>
        public static string HashName(this HashAlgorithm alg)
        {
            return alg.GetType().Name.Replace("CryptoServiceProvider","");
        }
    }


    /// <summary>
    ///   The main class that computes and prints the SHA1 or MD5 hash.
    /// </summary>
    public class hash
    {
        string _filename;
        string _text;
        static string _programName;

        /// <summary>
        ///   static constructor sets the programName - used to invoke this program.
        /// </summary>
        static hash()
        {
            String[] argv = Environment.GetCommandLineArgs();
            _programName = Path.GetFileName(argv[0]); // no extension
        }


        // ctor
        public hash (string[] args)
        {
            for (int i=0; i < args.Length; i++)
            {
                switch (args[i])
                {
                    case "-?":
                        throw new ArgumentException(args[i]);

                    case "-t":
                        if (_text!=null || _filename != null)
                            throw new ArgumentException(args[i]);
                        if (i+1 > args.Length-1)
                            throw new ArgumentException(args[i]);
                        i++;
                        _text = args[i];
                        break;

                    case "-f":
                        if (_text != null || _filename !=null)
                            throw new ArgumentException(args[i]);
                        if (i+1 > args.Length-1)
                            throw new ArgumentException(args[i]);
                        i++;
                        _filename = args[i];
                        break;

                    case "-md5":
                        _programName = "md5";
                        break;

                    case "-sha1":
                        _programName = "sha1";
                        break;

                    default:
                        throw new ArgumentException(args[i]);

                }
            }

            // validate
            if (_filename == null && _text==null)
                throw new ArgumentException();
        }


        public void Run()
        {
            HashAlgorithm[] hashes = null;

            // alter behavior based on my own program name
            if (String.Equals("md5", _programName, StringComparison.OrdinalIgnoreCase))
                hashes = new HashAlgorithm[] { MD5.Create() };
            else if (String.Equals("sha1", _programName, StringComparison.OrdinalIgnoreCase))
                hashes = new HashAlgorithm[] { SHA1.Create() };
            else hashes = new HashAlgorithm[] { MD5.Create(), SHA1.Create() };

            foreach (var h in hashes)
            {
                byte[] h1 = null;
                if (_filename != null)
                {
                using (var fs = File.Open(_filename,  FileMode.Open, FileAccess.Read ))
                {
                    h1 = h.ComputeHash(fs);
                }
                }
                else
                {
                    byte[] data = System.Text.Encoding.ASCII.GetBytes(_text);
                    h1 = h.ComputeHash(data);
                }
                System.Console.WriteLine("{0}{1}", (hashes.Length!=1)?(h.HashName()+": "):"",
                                         FormatByteArray(h1));
            }
        }


        /// <summary>
        ///   This does something similar to BitConverter.ToString(h1),
        ///   but without the dashes.
        /// </summary>
        private string FormatByteArray(byte[] b)
        {
            System.Text.StringBuilder sb1 = new System.Text.StringBuilder();
            for (int i = 0; i < b.Length; i++)
            {
                sb1.Append(System.String.Format("{0:X2}", b[i]));
            }
            return sb1.ToString();
        }


        public static void Usage()
        {
            if (_programName.ToLower().Equals("hash"))
                Console.WriteLine("\n{0}: computes the MD5 and SHA1 hashes for a file or string.\n", _programName);
            else
                Console.WriteLine("\n{0}: computes the {1} hash for a file or string.\n", _programName, _programName.ToUpper());

            Console.WriteLine("Usage:\n  {0} [ -md5 | -sha1 ] -f <filename>", _programName);
            Console.WriteLine("  {0} -t <text>", _programName);
        }


        public static void Main(string[] args)
        {
            try
            {
                new hash(args).Run();
            }
            catch (System.Exception exc1)
            {
                Console.WriteLine("Exception: {0}", exc1.ToString());
                Usage();
            }
        }
    }
}

